﻿using EpubFactory.Common;
using Markdig;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;

namespace EpubFactory.Lib99.Service
{
    public class Lib99Receipe : Receipe
    {
        private IEnumerable<Regex> removeTagRegexList = "acronym, bdo, big, cite, code, dfn, kbd, q, s, samp, strike, tt, u, var".Split(',').Select(g => new Regex(string.Format(@"<{0}>[\s\S]+?</{0}>", g.Trim()), RegexOptions.IgnoreCase | RegexOptions.Compiled));
        private Regex innerLinkRegex = new Regex(@"<a href=""/book/\d+/index.htm"">([\s\S]+?)</a>", RegexOptions.IgnoreCase | RegexOptions.Compiled);
        private Regex chapterDivRegex = new Regex(@"<div>([\s\S]+?)</div>", RegexOptions.IgnoreCase | RegexOptions.Compiled);
        private Regex sortedChapterDivRegex = new Regex(@"<div class=""chapter"">([\s\S]+?)</div>", RegexOptions.IgnoreCase | RegexOptions.Compiled);
        private Regex chapterSortNoRegex = new Regex(@"[A-Z]+%", RegexOptions.IgnoreCase | RegexOptions.Compiled);
        private Regex imgRegex = new Regex(@"<img src=""([\s\S]+?)""", RegexOptions.IgnoreCase | RegexOptions.Compiled);
        private Regex metaRegex = new Regex(@"<meta name=""client"" content=""([\w=]+)"" />", RegexOptions.IgnoreCase | RegexOptions.Compiled);
        private Regex contentRegex = new Regex(@"<div id=""content"">([\s\S]+)</div>", RegexOptions.IgnoreCase | RegexOptions.Compiled);
        private Regex introRegex = new Regex(@"<div class=""intro"">([\s\S]+?)</div>", RegexOptions.IgnoreCase | RegexOptions.Compiled);
        private Regex authorRegex = new Regex(@"<div>作者：<a href="".+?"" title="".+?"">(\w+?)</a></div>", RegexOptions.IgnoreCase | RegexOptions.Compiled);
        public Lib99Receipe()
            : base(() =>
            {
                var client = new HttpClient(new HttpClientHandler()
                {
                    AllowAutoRedirect = false
                });
                client.DefaultRequestHeaders.Add("UserAgent", PC_USER_AGENT);
                client.DefaultRequestHeaders.Add("Referer", "http://www.99lib.net");
                return client;
            })
        {

        }        

        public override Book GetBook()
        {
            var book = GetEmptyBook();
            book.Host = ExtractHost(entrypoint);
            var html = GetCachedContent(this.entrypoint);
            var doc = new HtmlAgilityPack.HtmlDocument();
            doc.LoadHtml(html);

            var h2 = doc.DocumentNode.Descendants("h2").FirstOrDefault();
            if (h2 != null)
            {
                book.Title = h2.InnerText;
            }
            var h4 = doc.DocumentNode.Descendants("h4").FirstOrDefault();
            if (h4 != null)
            {
                book.Authors.Add(h4.Descendants("a").Select(g => g.InnerText).FirstOrDefault());
            }
            else
            {
                book.Authors.Add(authorRegex.Match(html).Groups[1].Value);
            }
            var img = doc.DocumentNode.Descendants("img").FirstOrDefault();
            if (img != null)
            {
                book.CoverUrl = img.GetAttributeValue("src", string.Empty);
                if (!string.IsNullOrWhiteSpace(book.CoverUrl))
                {
                    book.CoverUrl = book.Host + book.CoverUrl;
                }
            }
            book.Summary = introRegex.Match(html).Groups[1].Value;

            ParseChapterLinks(book, doc);
            book.EPubId = ExtractBookId(entrypoint);

            return book;
        }

        private void ParseChapterLinks(Book book, HtmlAgilityPack.HtmlDocument doc)
        {
            var dl = doc.DocumentNode.Descendants("dl").Where(g => g.Id == "dir").FirstOrDefault();
            int sortNo = 0;
            if (dl != null)
            {
                foreach (var item in dl.ChildNodes)
                {
                    if (item.Name.Equals("dt"))
                    {
                        book.Chapters.Add(new Chapter()
                        {
                            Href = string.Empty,
                            SortOrder = sortNo,
                            Children = new List<Chapter>(),
                            Title = item.InnerText
                        });
                        sortNo++;
                    }
                    else if (item.Name.Equals("dd"))
                    {
                        var anchor = item.Descendants("a").First();
                        var chapter = new Chapter()
                        {
                            Href = book.Host + anchor.GetAttributeValue("href", string.Empty),
                            Title = anchor.InnerText,
                            SortOrder = sortNo
                        };
                        if (book.Chapters != null)
                        {
                            if (book.Chapters.Any() && book.Chapters.Last().Children != null)
                            {
                                var parent = book.Chapters.Last();
                                chapter.Parent = parent;
                                parent.Children.Add(chapter);
                            }
                            else
                            {
                                book.Chapters.Add(chapter);
                            }
                        }
                        else
                        {
                            log.Error("chapters is null book url:{0}", this.entrypoint);
                        }
                        sortNo++;
                    }
                    else
                    {
                        log.Warn("unkown tag name {0}-{1}", item.Name, item.InnerHtml);
                    }
                }
            }
        }


        public override void CleanChapter(Chapter chapter)
        {
            var html = chapter.SourceContent;
            if (string.IsNullOrWhiteSpace(html))
            {
                chapter.SourceContent = GetContent(chapter.Href);
            }
            html = chapter.SourceContent;
            if (string.IsNullOrWhiteSpace(html))
            {
                log.Error("cannot find html {0}", chapter.Href);
                return;
            }
            foreach (var regex in removeTagRegexList)
            {
                html = regex.Replace(html, "");
            }
            html = innerLinkRegex.Replace(html, (Match match) => { return match.Groups[1].Value; });

            var contentHtml = contentRegex.Match(html).Groups[1].Value;

            List<string> paragraphList = new List<string>();

            foreach (Match match in chapterDivRegex.Matches(contentHtml))
            {
                paragraphList.Add(match.Groups[1].Value.Replace("<div class=\"imgbox center\">", string.Empty));
            }
            var metaMatch = metaRegex.Match(html);
            if (metaMatch == null || !metaMatch.Success)
            {
                chapter.Content = string.Join("\n", paragraphList);
                return;
            }
            string content = Encoding.ASCII.GetString(Convert.FromBase64String(metaMatch.Groups[1].Value));
            var chapterNoList = chapterSortNoRegex.Split(content).Where(g => !string.IsNullOrWhiteSpace(g)).Select(g => Int32.Parse(g));

            if (chapterNoList.Count() != paragraphList.Count)
            {
                log.Error("cannot sort, len not match,url:{0},sort_len:{1},p_len:{2}", chapter.Href, chapterNoList.Count(), paragraphList.Count);
                chapter.Content = string.Empty;
                return;
            }

            int i = 0, j = 0;
            List<string> resultList = new List<string>(new string[paragraphList.Count]);

            foreach (var chapterNo in chapterNoList)
            {
                if (chapterNo < 5)
                {
                    resultList[chapterNo] = paragraphList[i];
                    j++;
                }
                else
                {
                    resultList[chapterNo - j] = paragraphList[i];
                }
                i++;
            }
            if (resultList.Any(d => d == null))
            {
                log.Error("sort p failed,has None item,url:{0},sort_len:{1},p_len:{2}", chapter.Href, chapterNoList.Count(), paragraphList.Count);
            }

            List<string> sortedParagraphList = new List<string>();

            foreach (Match match in sortedChapterDivRegex.Matches(html))
            {
                sortedParagraphList.Add(match.Groups[1].Value.Replace("<div class=\"imgbox center\">", string.Empty));
            }

            chapter.Content = string.Join("\n", sortedParagraphList) + string.Join("\n", resultList);
        }

        public override void GetImages(Chapter chapter)
        {
            List<KeyValuePair<string, string>> subList = new List<KeyValuePair<string, string>>();
            var baseDir = GetBookPath(chapter.Book);
            foreach (Match match in imgRegex.Matches(chapter.Content))
            {
                var url = match.Groups[1].Value;
                var fileName = url.Substring(url.LastIndexOf('/') + 1);
                if (!string.IsNullOrWhiteSpace(fileName))
                {
                    try
                    {
                        var filePath = Path.Combine(baseDir, fileName);
                        Download(url, filePath);
                        subList.Add(new KeyValuePair<string, string>(match.Groups[0].Value, fileName));
                        chapter.Book.Images.Add(filePath);
                    }
                    catch (HttpRequestException)
                    {
                        log.Warn("download file failed from  {0} ", url);
                    }
                }
                else
                {
                    log.Warn("parse file name from match {0} failed", url);
                }

                var sb = new StringBuilder(chapter.Content);
                foreach (var kvp in subList)
                {
                    sb.Replace(kvp.Key, string.Format("<img src=\"images/{0}\" ", kvp.Value));
                }

                chapter.Content = sb.ToString();
            }
        }

        public override string ExtractBookId(string url)
        {
            string bookId = url.Substring(0, url.LastIndexOf('/'));
            bookId = bookId.Substring(bookId.LastIndexOf('/') + 1);
            return bookId;
        }

        public override string Name
        {
            get { return "lib99"; }
        }

        public override IEnumerable<Uri> SourceUriList
        {
            get
            {
                return new string[] { "http://www.99lib.net", "http://www.hetushu.com" }.Select(g => new Uri(g));
            }
        }

    }
}
